﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using DotNetOpenAuth.AspNet;
using Microsoft.AspNet.Membership.OpenAuth;
using Newtonsoft.Json;
using testBasic.WebUI.Models;

namespace testBasic.WebUI.Controllers {

    public class AccountController : Controller {

        [AllowAnonymous]
        public ActionResult Register() {
            return View();
        }

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Register(RegisterModel model) {
            if (ModelState.IsValid) {
                // Tentative d'inscription de l'utilisateur
                try {
                    Membership.CreateUser(model.UserName, model.Password);
                    FormsAuthentication.SetAuthCookie(model.UserName, false);
                    return RedirectToAction("Index", "Home");
                } catch (MembershipCreateUserException e) {
                    ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
                }
            }

            // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire
            return View(model);
        }


        #region Login

        [HttpGet]
        [AllowAnonymous]
        public ActionResult Login(String returnUrl) {
            ViewBag.ReturnUrl = returnUrl;
            return View();
        }

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Login(LoginModel model, String returnUrl) {
            if (ModelState.IsValid) {
                if (Membership.ValidateUser(model.UserName, model.Password)) {
                    FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
                    return RedirectToLocal(returnUrl);
                } else {
                    ModelState.AddModelError("", "The user name or password provided is incorrect.");
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult LogOff() {
            FormsAuthentication.SignOut();            
            return RedirectToAction("Index", "Home");
        }

        #endregion

        #region Manage

        public ActionResult Manage(ManageMessageId? message) {
            ViewBag.StatusMessage =
                message == ManageMessageId.ChangePasswordSuccess ? "Votre mot de passe a été modifié."
                : message == ManageMessageId.SetPasswordSuccess ? "Votre mot de passe a été défini."
                : message == ManageMessageId.RemoveLoginSuccess ? "La connexion externe a été supprimée."
                : "";
            ViewBag.HasLocalPassword = OpenAuth.HasLocalPassword(User.Identity.Name);
            ViewBag.ReturnUrl = Url.Action("Manage");
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Manage(LocalPasswordModel model) {
            bool hasLocalPassword = OpenAuth.HasLocalPassword(User.Identity.Name);            
            ViewBag.ReturnUrl = Url.Action("Manage");
            if (hasLocalPassword) {
                if (ModelState.IsValid) {
                    // ChangePassword va lever une exception plutôt que de renvoyer la valeur False dans certains scénarios de défaillance.
                    bool changePasswordSucceeded;
                    try {
                        MembershipUser user = Membership.GetUser(User.Identity.Name);
                        changePasswordSucceeded = user.ChangePassword(model.OldPassword, model.NewPassword);
                    } catch (Exception) {
                        changePasswordSucceeded = false;
                    }

                    if (changePasswordSucceeded) {
                        return RedirectToAction("Manage", new { Message = ManageMessageId.ChangePasswordSuccess });
                    } else {
                        ModelState.AddModelError("", "Le mot de passe actuel est incorrect ou le nouveau mot de passe n'est pas valide.");
                    }
                }
            } else {
                // L'utilisateur n'a pas de mot de passe local. Veuillez donc supprimer les erreurs de validation provoquées par un
                // champ OldPassword manquant
                ModelState state = ModelState["OldPassword"];
                if (state != null) {
                    state.Errors.Clear();
                }

                if (ModelState.IsValid) {
                    try {
                        OpenAuth.AddLocalPassword(User.Identity.Name, model.NewPassword);
                        return RedirectToAction("Manage", new { Message = ManageMessageId.SetPasswordSuccess });
                    } catch (Exception) {
                        ModelState.AddModelError("", String.Format("Le compte local ne peut pas être créé. Un compte portant le même nom \"{0}\" existe peut-être déjà.", User.Identity.Name));
                    }
                }
            }

            // Si nous sommes arrivés là, quelque chose a échoué, réafficher le formulaire
            return View(model);
        }

        #endregion

        #region External Login

        [AllowAnonymous]
        [ChildActionOnly]
        public ActionResult ExternalLoginsList(String returnUrl) {
            ViewBag.ReturnUrl = returnUrl;
            return PartialView("_ExternalLoginsListPartial", OpenAuth.AuthenticationClients.GetAll());
        }

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult ExternalLogin(String provider, String returnUrl) {
            return new ExternalLoginResult(provider, Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
        }

        [AllowAnonymous]
        public ActionResult ExternalLoginCallback(String returnUrl) {
            AuthenticationResult result = OpenAuth.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
            if (!result.IsSuccessful) {
                return RedirectToAction("ExternalLoginFailure");
            }

            if (OpenAuth.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false)) {
                return RedirectToLocal(returnUrl);
            }

            if (User.Identity.IsAuthenticated) {
                // Si l'utilisateur actuel est connecté, ajoutez le nouveau compte
                OpenAuth.AddAccountToExistingUser(result.Provider, result.ProviderUserId, result.UserName, User.Identity.Name);
                return RedirectToLocal(returnUrl);
            } else {
                // L'utilisateur est nouveau. Demander le nom d'appartenance souhaité
                string loginData = JsonConvert.SerializeObject(result);
                ViewBag.ProviderDisplayName = OpenAuth.GetProviderDisplayName(result.Provider);
                ViewBag.ReturnUrl = returnUrl;
                return View("ExternalLoginConfirmation", new RegisterExternalLoginModel { UserName = result.UserName, ExternalLoginData = loginData });
            }
        }

        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult ExternalLoginConfirmation(RegisterExternalLoginModel model, string returnUrl) {
            string provider = null;
            string providerUserId = null;
            string providerUserName = null;

            if (User.Identity.IsAuthenticated || !TryDeserializeProviderUserId(model.ExternalLoginData, out provider, out providerUserId, out providerUserName)) {
                return RedirectToAction("Manage");
            }

            if (ModelState.IsValid) {
                // Insérer un nouvel utilisateur dans la base de données
                MembershipUser user = Membership.GetUser(model.UserName);
                
                if (user == null) {
                    
                    OpenAuth.CreateUser(provider, providerUserId, providerUserName, model.UserName);
                    OpenAuth.Login(provider, providerUserId, createPersistentCookie: false);

                    return RedirectToLocal(returnUrl);
                } else {
                    ModelState.AddModelError("UserName", "Le nom d'utilisateur existe déjà. Entrez un nom d'utilisateur différent.");
                }

            }

            ViewBag.ProviderDisplayName = OpenAuth.GetProviderDisplayName(provider);
            ViewBag.ReturnUrl = returnUrl;
            return View(model);
        }        

        [AllowAnonymous]
        public ActionResult ExternalLoginFailure() {
            return View();
        }

        [ChildActionOnly]
        public ActionResult RemoveExternalLogins() {
            IEnumerable<OpenAuthAccountData> accounts = OpenAuth.GetAccountsForUser(User.Identity.Name);
            List<ExternalLogin> externalLogins = new List<ExternalLogin>();
            foreach (OpenAuthAccountData account in accounts) {
                externalLogins.Add(new ExternalLogin {
                    Provider = account.ProviderName,
                    ProviderDisplayName = account.ProviderDisplayName,
                    ProviderUserId = account.ProviderUserId,
                });
            }

            ViewBag.ShowRemoveButton = externalLogins.Count > 1 || OpenAuth.HasLocalPassword(User.Identity.Name);
            return PartialView("_RemoveExternalLoginsPartial", externalLogins);
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Disassociate(string provider, string providerUserId) {
            ManageMessageId? message = null;

            // Utiliser une transaction pour empêcher l'utilisateur de supprimer ses dernières informations d'identification de connexion
            using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.Serializable })) {
                bool hasLocalPassword = OpenAuth.HasLocalPassword(User.Identity.Name);
                if (hasLocalPassword || OpenAuth.GetAccountsForUser(User.Identity.Name).Count() > 1) {
                    OpenAuth.DeleteAccount(User.Identity.Name, provider, providerUserId);
                    scope.Complete();
                    message = ManageMessageId.RemoveLoginSuccess;
                }
            }
            
            return RedirectToAction("Manage", new { Message = message });
        }

        #endregion

        #region Plumbing

        public enum ManageMessageId {
            ChangePasswordSuccess,
            SetPasswordSuccess,
            RemoveLoginSuccess,
        }

        private Boolean TryDeserializeProviderUserId(String json, out String provider, out String providerUserId, out String providerUserName) {
            try {
                JsonAuthenticationResult result = JsonConvert.DeserializeObject<JsonAuthenticationResult>(json);
                provider = result.Provider;
                providerUserId = result.ProviderUserId;
                providerUserName = result.UserName;
            } catch {
                providerUserName = null;
                provider = null;
                providerUserId = null;
                return false;
            }
            return true;
        }

        private ActionResult RedirectToLocal(string returnUrl) {
            if (Url.IsLocalUrl(returnUrl)) {
                return Redirect(returnUrl);
            } else {
                return RedirectToAction("Index", "Home");
            }
        }

        internal class JsonAuthenticationResult {
            public String Provider { get; set; }
            public String ProviderUserId { get; set; }
            public String UserName { get; set; }
        }

        internal class ExternalLoginResult : ActionResult {
            public ExternalLoginResult(String provider, String returnUrl) {
                Provider = provider;
                ReturnUrl = returnUrl;
            }

            public string Provider { get; private set; }
            public string ReturnUrl { get; private set; }

            public override void ExecuteResult(ControllerContext context) {
                OpenAuth.RequestAuthentication(Provider, ReturnUrl);
            }
        }

        private static string ErrorCodeToString(MembershipCreateStatus createStatus) {
            // Consultez http://go.microsoft.com/fwlink/?LinkID=177550 pour
            // obtenir la liste complète des codes d'état.
            switch (createStatus) {
                case MembershipCreateStatus.DuplicateUserName:
                    return "Le nom d'utilisateur existe déjà. Entrez un nom d'utilisateur différent.";

                case MembershipCreateStatus.DuplicateEmail:
                    return "Un nom d'utilisateur pour cette adresse de messagerie existe déjà. Entrez une adresse de messagerie différente.";

                case MembershipCreateStatus.InvalidPassword:
                    return "Le mot de passe fourni n'est pas valide. Entrez une valeur de mot de passe valide.";

                case MembershipCreateStatus.InvalidEmail:
                    return "L'adresse de messagerie fournie n'est pas valide. Vérifiez la valeur et réessayez.";

                case MembershipCreateStatus.InvalidAnswer:
                    return "La réponse de récupération du mot de passe fournie n'est pas valide. Vérifiez la valeur et réessayez.";

                case MembershipCreateStatus.InvalidQuestion:
                    return "La question de récupération du mot de passe fournie n'est pas valide. Vérifiez la valeur et réessayez.";

                case MembershipCreateStatus.InvalidUserName:
                    return "Le nom d'utilisateur fourni n'est pas valide. Vérifiez la valeur et réessayez.";

                case MembershipCreateStatus.ProviderError:
                    return "Le fournisseur d'authentification a retourné une erreur. Vérifiez votre entrée et réessayez. Si le problème persiste, contactez votre administrateur système.";

                case MembershipCreateStatus.UserRejected:
                    return "La demande de création d'utilisateur a été annulée. Vérifiez votre entrée et réessayez. Si le problème persiste, contactez votre administrateur système.";

                default:
                    return "Une erreur inconnue s'est produite. Vérifiez votre entrée et réessayez. Si le problème persiste, contactez votre administrateur système.";
            }
        }

        #endregion

    }
}
